package com.yanmaohu.nikita.configuration;

import com.yanmaohu.nikita.utility.ConfigurationUtility;
import com.yanmaohu.nikita.utility.TokenUtility;
import com.yanmaohu.nikita.utility.ValueUtility;

import java.io.*;
import java.util.*;

/**
 * @author Bright Lee
 */
public final class Configuration {

    private final File PROJECT_ROOT_DIRECTORY;
    private final File SOURCE_CODE_DIRECTORY;
    private final Properties SETTING_PROPERTIES = new Properties();
    private final Map<String, File> SOURCE_CODE_FILE_MAP = new LinkedHashMap<>();

    private boolean printStackTrace = false;
    private boolean printCompilationInfo = false;
    private boolean printCompilationWarn = true;

    public Configuration(String projectRootDirectoryPath) {
        PROJECT_ROOT_DIRECTORY = new File(projectRootDirectoryPath);
        if (!PROJECT_ROOT_DIRECTORY.exists()) {
            throw new RuntimeException("The project root directory does not exist, "
                    + PROJECT_ROOT_DIRECTORY.getAbsolutePath());
        }
        if (!this.PROJECT_ROOT_DIRECTORY.isDirectory()) {
            throw new RuntimeException("The specified project root directory is not a directory, "
                    + PROJECT_ROOT_DIRECTORY.getAbsolutePath());
        }
        final File configDirectory = new File(this.PROJECT_ROOT_DIRECTORY, ValueUtility.CONFIG_DIRECTORY_NAME);
        final File settingsFile = new File(configDirectory, ValueUtility.SETTINGS_FILE_NAME);
        if (settingsFile.exists()) {
            if (settingsFile.isDirectory()) {
                throw new RuntimeException("Settings file is not a file, " + settingsFile.getAbsolutePath());
            }
            Reader settingsPropertiesFileReader = null;
            try {
                settingsPropertiesFileReader = new BufferedReader(
                        new InputStreamReader(
                                new FileInputStream(settingsFile),
                                ValueUtility.FILE_ENCODING
                        ),
                        ValueUtility.FILE_BUFFER_SIZE);
                SETTING_PROPERTIES.load(settingsPropertiesFileReader);
                String rootPackage = SETTING_PROPERTIES.getProperty(ValueUtility.ROOT_PACKAGE_SETTING_NAME);
                if (rootPackage == null || rootPackage.isBlank()) {
                    rootPackage = ValueUtility.DEFAULT_ROOT_PACKAGE;
                }
                rootPackage = ConfigurationUtility.trimRootPackage(rootPackage);
                SETTING_PROPERTIES.setProperty(ValueUtility.ROOT_PACKAGE_SETTING_NAME, rootPackage);
                String version = SETTING_PROPERTIES.getProperty(ValueUtility.VERSION_SETTING_NAME);
                if (version == null || version.isBlank()) {
                    version = ValueUtility.DEFAULT_VERSION;
                }
                version = ConfigurationUtility.trimVersion(version);
                SETTING_PROPERTIES.setProperty(ValueUtility.VERSION_SETTING_NAME, version);
            } catch (Exception e) {
                throw new RuntimeException("Failed to read settings file, "
                        + settingsFile.getAbsolutePath(),
                        e);
            } finally {
                if (settingsPropertiesFileReader != null) {
                    try {
                        settingsPropertiesFileReader.close();
                    } catch (IOException e) {
                        throw new RuntimeException("Failed to close settings file, "
                                + settingsFile.getAbsolutePath(),
                                e);
                    }
                }
            }
        }
        SOURCE_CODE_DIRECTORY = new File(this.PROJECT_ROOT_DIRECTORY, ValueUtility.SOURCE_CODE_DIRECTORY_NAME);
        if (SOURCE_CODE_DIRECTORY.exists()) {
            if (!SOURCE_CODE_DIRECTORY.isDirectory()) {
                throw new RuntimeException("The source code directory is not a directory, "
                        + SOURCE_CODE_DIRECTORY.getAbsolutePath());
            }
            File[] fs = SOURCE_CODE_DIRECTORY.listFiles();
            for (File f : fs) {
                loadSourceCodeFiles(SOURCE_CODE_FILE_MAP, f);
            }
        }
    }

    public void setPrintStackTrace(boolean printStackTrace) {
        this.printStackTrace = printStackTrace;
    }

    public boolean isPrintStackTrace() {
        return printStackTrace;
    }

    public void setPrintCompilationInfo(boolean printCompilationInfo) {
        this.printCompilationInfo = printCompilationInfo;
    }

    public boolean isPrintCompilationInfo() {
        return printCompilationInfo;
    }

    public void setPrintCompilationWarn(boolean printCompilationWarn) {
        this.printCompilationWarn = printCompilationWarn;
    }

    public boolean isPrintCompilationWarn() {
        return printCompilationWarn;
    }

    public String getRootPackage() {
        return SETTING_PROPERTIES.getProperty(ValueUtility.ROOT_PACKAGE_SETTING_NAME);
    }

    public String getProjectRootDirectoryAbsolutePath() {
        return PROJECT_ROOT_DIRECTORY.getAbsolutePath();
    }

    public String getSourceCodeDirectoryAbsolutePath() {
        String sourceCodeDirectoryPath = SOURCE_CODE_DIRECTORY.getAbsolutePath();
        if (!sourceCodeDirectoryPath.endsWith(File.separator)) {
            sourceCodeDirectoryPath += File.separator;
        }
        return sourceCodeDirectoryPath;
    }

    public List<File> getSourceCodeFileList() {
        List<File> sourceCodeFileList = new ArrayList<>(SOURCE_CODE_FILE_MAP.values());
        String sourceCodeDirectoryPath = getSourceCodeDirectoryAbsolutePath();
        StringBuilder buf = new StringBuilder();
        for (int i = 0; i < File.separator.length(); i++) {
            char c = File.separator.charAt(i);
            if (c == '\\') {
                buf.append('\\').append('\\');
            } else {
                buf.append(c);
            }
        }
        String separator = buf.toString();
        if (isPrintCompilationInfo()) {
            for (File sourceCodeFile : sourceCodeFileList) {
                String sourceCodeFilePath = sourceCodeFile.getAbsolutePath();
                sourceCodeFilePath = sourceCodeFilePath
                        .substring(sourceCodeDirectoryPath.length(),
                                sourceCodeFilePath.length() - ValueUtility.SOURCE_CODE_FILE_NAME_POSTFIX.length());
                sourceCodeFilePath = sourceCodeFilePath.replaceAll(separator, ".");
                System.out.println("-> " + sourceCodeFilePath);
            }
        }
        return sourceCodeFileList;
    }

    private void loadSourceCodeFiles(Map<String, File> files, File sourceCodeFile) {
        if (sourceCodeFile.isFile()) {
            String sourceCodeFilePath = sourceCodeFile.getAbsolutePath();
            if (!sourceCodeFilePath.endsWith(ValueUtility.SOURCE_CODE_FILE_NAME_POSTFIX)) {
                return;
            }
            String path = sourceCodeFilePath.substring(
                    getSourceCodeDirectoryAbsolutePath().length(),
                    sourceCodeFilePath.length() - ValueUtility.SOURCE_CODE_FILE_NAME_POSTFIX.length());
            if (path.startsWith(File.separator)) {
                path = path.substring(File.separator.length());
            }
            StringBuilder buf = new StringBuilder(path.length());
            for (int i = 0; i < path.length(); i++) {
                char c = path.charAt(i);
                if (c == File.separatorChar) {
                    buf.append('.');
                } else {
                    buf.append(c);
                }
            }
            path = buf.toString();
            String[] a = path.split(".");
            for (String s : a) {
                if (!TokenUtility.isIdentifier(s)) {
                    return;
                }
            }
            files.put(path, sourceCodeFile);
            return;
        }
        File[] fs = sourceCodeFile.listFiles();
        for (File f : fs) {
            loadSourceCodeFiles(files, f);
        }
    }

}
